package com.mypro.http;

import java.io.IOException;
import java.io.InputStream;
import java.net.SocketTimeoutException;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Date: 17/12/11 下午3:22
 * Description:
 */
public class HttpClientUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientUtil.class);

    private static PoolingHttpClientConnectionManager httpClientConnectionManager = new PoolingHttpClientConnectionManager();
    private static CloseableHttpClient httpClient = null;
    private static CloseableHttpClient httpsClient = null;
    private static HttpClientUtil.HttpConfig httpConfig = null;
    private static SSLConnectionSocketFactory sslsf = null;
    // 连接池最大连接数和每路由最大连接数可通过httpconf.properties进行配置
    private static final String HTTP_CONF_FILE_NAME = "fenxiao-httpconf.properties";
    private static final int MAX_TOTAL_CONNECTION = 800;
    private static final int MAX_PER_ROUTE = 150;
    private static String UTF_8 = "UTF-8";
    // 默认超时时间(单位毫秒)
    public static final int DEFAULT_TIMEOUT = 5000;

    interface ContentType {
        String FORM_URLENCODED = "application/x-www-form-urlencoded";
        String JSON = "application/json; charset=utf-8";
    }

    interface AcceptType {
        String ACCEPT_JSON = "application/json";
        String ACCEPT_ANNY = "*/*";
    }

    private HttpClientUtil() {
        // prevent instantialization
    }

    static {
        loadConf();
        httpClientConnectionManager.setMaxTotal(httpConfig.getMaxTotalConnection());  // 连接池最大连接数
        httpClientConnectionManager.setDefaultMaxPerRoute(httpConfig.getMaxPerRoute());  // 每路由最大连接数

        // 通过连接池获取的httpClient
        httpClient = HttpClients.custom().setConnectionManager(httpClientConnectionManager).build();
        // 通过连接池获取的httpsClient, 能支持https
        httpsClient = HttpClients.custom().setSSLSocketFactory(sslsf).setConnectionManager(httpClientConnectionManager).build();
        LOGGER.info("HttpClient initialization");
    }

    /**
     * 如果配置httpconf.properties, 则从该文件中读取http连接池的配置参数, 否则使用默认值
     */
    private static void loadConf() {
        if (httpConfig == null) {
            httpConfig = new HttpConfig();
        }
        Properties properties = new Properties();
        try {
            InputStream inputStream = HttpClientUtil.class.getClassLoader().getResourceAsStream(HTTP_CONF_FILE_NAME);

            if (inputStream == null) {
                LOGGER.warn("httpConfig file={} does not exist", HTTP_CONF_FILE_NAME);
                return;
            }
            properties.load(inputStream);
            int maxTotalConnection = Integer.parseInt(properties.getProperty("max_total_connection"));
            int maxPerRoute = Integer.parseInt(properties.getProperty("max_per_route"));
            LOGGER.info("max_total_connection={}, max_per_route={}", maxTotalConnection, maxPerRoute);
            httpConfig.setMaxTotalConnection(maxTotalConnection);
            httpConfig.setMaxPerRoute(maxPerRoute);
        } catch (IOException e) {
            LOGGER.warn("read httpConfig from file={} failed", HTTP_CONF_FILE_NAME, e);
        } catch (NumberFormatException e) {
            LOGGER.warn("read httpConfig from file={} failed", HTTP_CONF_FILE_NAME, e);
        } catch (Exception e) {
            LOGGER.warn("read httpConfig from file={} failed", HTTP_CONF_FILE_NAME, e);
        }
    }

    /**
     * https
     */
    private static void initHttps() {
        X509TrustManager trustManager = new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };
        try {
            SSLContext sslContext = SSLContext.getInstance("TLS");
            // 初始化SSL上下文
            sslContext.init(null, new TrustManager[] {trustManager}, null);
            // SSL套接字连接工厂,NoopHostnameVerifier为信任所有服务器
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
        } catch (NoSuchAlgorithmException | KeyManagementException e) {
            LOGGER.error("初始化https支持失败", e);
        }
    }

    static class HttpConfig {
        private int maxTotalConnection = MAX_TOTAL_CONNECTION;
        private int maxPerRoute = MAX_PER_ROUTE;

        public int getMaxTotalConnection() {
            return maxTotalConnection;
        }

        public void setMaxTotalConnection(int maxTotalConnection) {
            this.maxTotalConnection = maxTotalConnection;
        }

        public int getMaxPerRoute() {
            return maxPerRoute;
        }

        public void setMaxPerRoute(int maxPerRoute) {
            this.maxPerRoute = maxPerRoute;
        }
    }

    public static String invokeGet(String url, Map<String, String> headers, Map<String, String> params) throws Exception {
        return invokeGet(url, headers, params, DEFAULT_TIMEOUT);
    }

    public static String invokeGet(String url, Map<String, String> headers, Map<String, String> params, int timeout) throws Exception {
        URIBuilder uriBuilder = new URIBuilder();
        valueForUriBuilder(url, uriBuilder);

        if (params != null && params.size() > 0) {
            List<NameValuePair> pairs = new ArrayList<NameValuePair>();
            for (Map.Entry<String, String> param : params.entrySet()) {
                pairs.add(new BasicNameValuePair(param.getKey(), param.getValue()));
            }
            uriBuilder.setParameters(pairs);
        }

        HttpGet httpGet = new HttpGet(uriBuilder.build());
        RequestConfig config = buildRequestConfig(timeout, timeout);
        httpGet.setConfig(config);

        if (headers != null && headers.size() > 0) {
            for (Map.Entry<String, String> param : headers.entrySet()) {
                httpGet.addHeader(param.getKey(), param.getValue());
            }
        }

        return sendRequest(url, httpGet);
    }

    public static String invokePost(String url, Map<String, String> headers, Map<String, String> params) throws Exception {
        return invokePost(url, headers, params, DEFAULT_TIMEOUT);
    }

    public static String invokePost(String url, Map<String, String> headers, Map<String, String> params, int timeout) throws Exception {
        URIBuilder uriBuilder = new URIBuilder();
        valueForUriBuilder(url, uriBuilder);

        HttpPost httpPost = new HttpPost(uriBuilder.build());
        RequestConfig config = buildRequestConfig(timeout, timeout);
        httpPost.setConfig(config);

        if (headers != null && headers.size() > 0) {
            for (Map.Entry<String, String> param : headers.entrySet()) {
                httpPost.addHeader(param.getKey(), param.getValue());
            }
        }

        if (params != null && params.size() > 0) {
            List<NameValuePair> pairs = new ArrayList<NameValuePair>();
            for (Map.Entry<String, String> param : params.entrySet()) {
                pairs.add(new BasicNameValuePair(param.getKey(), param.getValue()));
            }
            httpPost.setEntity(new UrlEncodedFormEntity(pairs, UTF_8));
        }

        return sendRequest(url, httpPost);
    }

    public static String invokePost(String url, Map<String, String> headers, String body) throws Exception {
        URIBuilder uriBuilder = new URIBuilder();
        valueForUriBuilder(url, uriBuilder);

        HttpPost httpPost = new HttpPost(uriBuilder.build());

        if (headers != null && headers.size() > 0) {
            for (Map.Entry<String, String> param : headers.entrySet()) {
                httpPost.addHeader(param.getKey(), param.getValue());
            }
        }

        if (body != null) {
            httpPost.setEntity(new StringEntity(body, Charset.forName(UTF_8)));
        }

        return sendRequest(url, httpPost);
    }

    // POST方式发送json数据体
    public static String invokePost(String url, String body) throws Exception {
        return invokePost(url, body, "application/json; charset=utf-8", "application/json");
    }

    public static String invokePost(String url, String body, String contentType, String accept) throws Exception {
        URIBuilder uriBuilder = new URIBuilder();
        valueForUriBuilder(url, uriBuilder);

        HttpPost httpPost = new HttpPost(uriBuilder.build());

        httpPost.addHeader("Content-type", contentType);
        httpPost.setHeader("Accept", accept);
        httpPost.setEntity(new StringEntity(body, Charset.forName(UTF_8)));

        return sendRequest(url, httpPost);
    }

    public static String invokeGet(String url) throws Exception {
        // 原始访问url
        HttpGet httpGet = new HttpGet(url);

        return sendRequest(url, httpGet);
    }

    public static HttpResponse invokePost(HttpRequestBase request) throws IOException {
        try {
            CloseableHttpResponse response = httpClient.execute(request);
            return response;
        } finally {
            request.releaseConnection();
        }
    }

    private static String sendRequest(String url, HttpRequestBase request) throws Exception {
        CloseableHttpClient client;
        if (url.startsWith("https")) {
            client = httpsClient;
        } else {
            client = httpClient;
        }
        long st = System.currentTimeMillis();
        String responseStr;
        int status = 0;
        try {
            CloseableHttpResponse response = client.execute(request);
            HttpEntity entity = response.getEntity();
            status = response.getStatusLine().getStatusCode();
            if (entity != null) {
                responseStr = EntityUtils.toString(entity, UTF_8);
                response.close();
            } else {
                responseStr = StringUtils.EMPTY;
            }
        } catch (SocketTimeoutException e) {
            LOGGER.error("HttpClient.sendRequest, url:{}, use_time:{} ms", request.getURI(), (System.currentTimeMillis() - st), e);
            throw e;
        } catch (ClientProtocolException e) {
            LOGGER.error("HttpClient.sendRequest, url:{}, use_time:{} ms", request.getURI(), (System.currentTimeMillis() - st), e);
            throw e;
        } catch (IOException e) {
            LOGGER.error("HttpClient.sendRequest, url:{}, use_time:{} ms", request.getURI(), (System.currentTimeMillis() - st), e);
            throw e;
        } finally {
            request.releaseConnection();
        }
        LOGGER.info("HttpClient Success, status:{}, url:{}, use_time:{} ms", status, request.getURI(), (System.currentTimeMillis() - st));
        return responseStr;
    }

    private static RequestConfig buildRequestConfig(Integer connTimeout, Integer readTimeout) {
        RequestConfig.Builder customReqConf = RequestConfig.custom();
        if (connTimeout != null) {
            customReqConf.setConnectTimeout(connTimeout);
        }
        if (readTimeout != null) {
            customReqConf.setSocketTimeout(connTimeout);
        }
        return customReqConf.build();
    }

    private static void valueForUriBuilder(String url, URIBuilder uriBuilder) {
        Integer apartIndex = url.indexOf("?");
        if (apartIndex == -1) {
            uriBuilder.setPath(url);
        } else {
            uriBuilder.setPath(url.substring(0, apartIndex));
            uriBuilder.setCustomQuery(url.substring(apartIndex + 1, url.length()));
        }
    }

    public static void main(String[] args) throws Exception {
        String str
            = "https://sns.testing.amap.com/ws/boss/order/hotel/callback/order?ent=1&in=ktR2aToOO8Qdxucl2Lt0e1hy1jfqvmPE2tvfboK0BAXV%2F1lBa7B%2BYu2d%2FmPRUE85rzNohEwqrtXvk"
            + "%2BaoA3Ktkgn41ceffVVBk3ZBEUg7OeRWHRLYvZIc3xtzLR9t5Gpr0oU9noNoftZHq5QyWrNwKlEWlya03%2BLH4rThKj0Tj49jRQ%2BOoZjArEUQTJhheLRsIj1gl5x4G%2BKa5BEqcXc8c56LdzZAu8"
            + "%2BxhBi80KeQIqrrJp74V0wIYH2lcr8jT8lsDa44QB8IaRKDNdBUANhNiepKERxE0oJFduS%2BV7xaeHeSJLGm4zkzZ8%2FOaEKQttIUdUgFKyBgAdftI4QlJpCRK9IIhFfdchzbSN7ec76I"
            + "%2Bs4FK2cuYKVKrhfz9EIquh4oHXFN5KEDx7LDtvn0Ckrmx2FSalcOXyXsV3Pd6ZoNOn81WA5qFatKL8XQ%2FhoYrPnyZcYxW0clYuyi%2FKnZ8Vi8osvI6KfqffPYheTs%2FyWFqDvf82BO%2BKFbHv6jUKB4f"
            + "%2BO2LMeZtzSy9diD78Xp6bhZnzSTNDifJuaAPZWNVDCNT9MOeh9pfiCPvLSpe1nbcn5ONw%2BzZgmUSH0XTu%2Bt%2Bli60Gt3TslL%2F5ZXELhZiPoqS5jz%2FTA2oZCYZ6riF0xsD9Bb%2B%2BQk0uNk0nofd0ImQWvG%2BIDG7%2BMGDabyyRLh1Rn6I%2Fyg%2B1twop2d0Qz"
            + "%2FPJS5gqg8v1qDpEVmZ2QBgKLLfpBfSXCJ6inPJR9G3VO%2FXAaSZpTsuHC3zjqEQSXxay0i%2F7KMThkou9ZBISsZg337GreNO981dc7DSO6YD0bI8%2FEzKDqy%2B1HXR0uEaLBpsuERj5gddRd%2F0T1M2wlwKxKzH1Gj2IY4zcnO23zUbaihkA4O%2F%2F7dvtgDS1viX%2BcjvT"
            + "%2BGg4go1QYXs0OwSqXxVSG8NLmNVU1eu262x58qYCfPVNtC05xHNUPKHC%2F87jZCtrX5Nwelb6RX%2BS1bx7rQR336bOURx8f6Sw923fImdZb%2ByHPObJUIk%2F5GxHb53%2Bp5owHio%2BWNMbOuY3HwgTlIqshxJ"
            + "%2BMu6w1dsPmG6kk1VsXdyYUBRV1Uu71a1oQnwlWBXsDiIfu9eYSXUeUK0rZ%2Bz%2BRniVxCB3ns7VD5uB5yIFKwz0zU%2Bscc%2FrDXn4E1ViWZj7M%2BblswROGwfNFJH4Bx4%2Ff"
            + "%2Brm1BamPRqVGzxbdxCYjkrm0Fev3vA3BT1WZ7VjJQ9weckbHXsU4ZfMftVIXdtAtoRJ9LsxrKxPloqPtCJMK3MaRkXQaNeJtJlM%2FMq%2BAH42SbrdC8OJPU002KAm2Y3OPJITxnPlT6yk%2F9dTNSFlgl0V2f8zumGXnzNiGd%2FFdKW2uMotSgwvcla9Ryq18011qdBfBZq"
            + "%2BYwHpS2JGGCIFX9ivLiPKL9jOmCFqruywsehEO47DwfwqNjvrVaINlSTu%2BXTJQcKhWq7LzSQ4saW5Kpm5U1z3cHKFFpd25BrObJB2%2BCl7Xmr20XsoNLIiQQlCsQArAJXF%2FWbJQZ9h1eYFLbCTotYHii4e%2BCMfK%2FPJ5zClSKsaYh1M%2Fr4RoZLBkrpIFKPloRVkrw%2F4RM"
            + "%2F7ZdaGnqV%2BMNgIiaVgnGowthV3kVcEWsURct3%2BtCluNCTNmzeS%2B5LweXzdzBpRTlGJasZRMlawbnbUfzRjzom%2FhS56Q5Rg5uixIs3TDOkQTCVr76H09IU7v5zo88CCieVEJpsoNoPCIph7F3AzYpQMAPD8YxnvGI8pUyHNCKDrDi0e0pGEuBehVU8vKTIJgUr3PZr"
            + "%2F5LkCDOSl67CfPPsH%2B4hbbNRIWQcLY9ylonVpqWPRrFNa1sEIlhENr0FE%2BEAWfjeCaWPwyZGdW9lDaMxIeSg7zutal2G38x18c1SdBDaqUqFnDVprVfKsILEjZK5pZZK%2FKmX4B4NcaQ6%2B0"
            + "%2FyzjK0ZpePaGhmd9DCZPYa2p6M6T2qTkDuenRWkHA2diKXtcv9OVG4VWigGnWDMlsv0xx2Rs36cc8iKmzYk0Rs2Y64p4ziBZGf8a2mA4CfJML9YQlLudHgIzCxdo%3D&seed=W2ZnzMmrLmzb3GeJ";
        //
        String responseStr = HttpClientUtil.invokeGet(str);
        System.out.println(responseStr);

        String url = "http://b.dianping.com/openapi/channelOrderQuery";
        String body
            = "token=ZZYES-POS&version=1.6&content=JcRCfBgHh21wuKoJhHk8SJVqZvgOVQs9i4PxrGd6VgPrBQEwgmVYfAM4kmhX-3Otumd3tx6pBPdFb3Jl1QSXFMWATLOlMAjmg1"
            + "-sIbXbyNHkxQvjegfU2YIvMslwQKZqc2VBTcWxDIFKcJcq_UkDQ7e9wvO1chk7MzS2Z1y8t_TLhceslis72BHdN61h7vq7YWlGupLMiPncOm0PeBe0_qI6gGmYODZ7npuV8W3oeRKT6INHJ3sA4MT7jAmejrC8";
        String aa = HttpClientUtil.invokePost(url, body, HttpClientUtil.ContentType.FORM_URLENCODED, HttpClientUtil.AcceptType.ACCEPT_JSON);
        System.out.println(aa);
    }
}
